﻿using System;
using System.Collections.Generic;

namespace TimeTravel.XnaLibrary.GamePlay
{
	/// <summary>
	/// Represents a Player in the TimeTravel game.
	/// </summary>
	public class Player
	{
		#region Fields
		private int mCoins;
		private int mFood;
		private Race mRace;
		private int mStartingPosition;
		private List<Unit> mActiveUnits;
		private Queue<Unit> mSleepingUnits;
		private Player mEnemy;
		private Unit mFirstUnit;

		private static int mDistanceToBoost = 32;
		/// <summary>
		/// Gets or sets the starting coins for each player
		/// </summary>
		public static int StartingCoins = 1000;
		/// <summary>
		/// Gets or sets the starting food for each player.
		/// </summary>
		public static int StartingFood = 500;
		/// <summary>
		/// Gets or sets the food earned per tile.
		/// </summary>
		public static int FoodPerTile = 1;
		/// <summary>
		/// Gets an instance of the Random class used for generating random numbers.
		/// </summary>
		public static Random Rng = new Random();
		#endregion

		#region Properties
		/// <summary>
		/// Gets the amount of coins the player currently has.
		/// </summary>
		public int Coins
		{
			get { return mCoins; }
		}

		/// <summary>
		/// Gets the amount of food this player currently has.
		/// </summary>
		public int Food
		{
			get { return mFood; }
			internal set { mFood = value; }
		}

		/// <summary>
		/// Gets the player`s race.
		/// </summary>
		public Race Race
		{
			get { return mRace; }
		}

		/// <summary>
		/// Gets or sets the starting position for each player`s unit.
		/// </summary>
		public int StartingPosition
		{
			get { return mStartingPosition; }
		}

		/// <summary>
		/// Gets a list of all the currently active units.
		/// </summary>
		public List<Unit> ActiveUnits
		{
			get { return mActiveUnits; }
		}

		/// <summary>
		/// Gets a list of all the currently sleeping units.
		/// </summary>
		public Queue<Unit> SleepingUnits
		{
			get { return mSleepingUnits; }
		}

		/// <summary>
		/// Gets or sets the current player`s enemy.
		/// </summary>
		public Player Enemy
		{
			get { return mEnemy; }
			set { mEnemy = value; }
		}
		#endregion

		/// <summary>
		/// Initializes a new Player instance with the specified race and starting position.
		/// </summary>
		/// <param name="race">The race of the player.</param>
		/// <param name="unitStartingPosition">The starting position of each player`s unit</param>
		public Player(Race race, int unitStartingPosition)
		{
			mRace = race;
			mStartingPosition = unitStartingPosition;
			mCoins = StartingCoins;
			mFood = StartingFood;
			mActiveUnits = new List<Unit>();
			mSleepingUnits = new Queue<Unit>();
		}

		/// <summary>
		/// Ininitializes a new Player instance by copying the specified player;
		/// </summary>
		/// <param name="player">The player to copy from.</param>
		public Player(Player player)
		{
			this.mCoins = player.mCoins;
			this.mFood = player.mFood;
			this.mStartingPosition = player.mStartingPosition;
			this.mRace = new Race(player.Race);
			this.mActiveUnits = new List<Unit>(player.mActiveUnits.Count);
			for (int i = 0; i < player.mActiveUnits.Count; i++)
			{
				this.mActiveUnits.Add(new Unit(player.mActiveUnits[i]));	
			}
			this.mSleepingUnits = new Queue<Unit>(player.mSleepingUnits.Count);
			while (mSleepingUnits.Count > 0)
			{
				this.mSleepingUnits.Enqueue(new Unit(player.mSleepingUnits.Dequeue()));
			}

		}

		#region Methods
		/// <summary>
		/// Returns a value indicating whether the player can buy the given unit.
		/// </summary>
		/// <param name="unitTier">The unit`s tier.</param>
		public bool CanBuyUnit(int unitTier)
		{
			return mRace.CanBuildUnit(unitTier) && mRace.Units[unitTier].GoldCost <= mCoins;
		}

		/// <summary>
		/// Tries to buy a new unit of specified tier.
		/// </summary>
		/// <param name="unitTier">The tier to buy the unit from.</param>
		public void BuyUnit(int unitTier)
		{
			if (CanBuyUnit(unitTier))
			{
				Unit newUnit = new Unit(mRace.Units[unitTier]);
				newUnit.Position = mStartingPosition;
				mSleepingUnits.Enqueue(newUnit);
				mCoins -= newUnit.GoldCost;
			}
		}

		/// <summary>
		/// Tries to upgrade the specified unit tier.
		/// </summary>
		/// <param name="unitTier">The tier to upgrade.</param>
		public void UpgradeUnit(int unitTier)
		{
			Unit unit = mRace.Units[unitTier];
			if (unit.UpgradeCost < mCoins)
			{
				unit.Upgrade();
				mCoins -= unit.UpgradeCost;
			}
		}

		/// <summary>
		/// Wakes up all currently sleeping units and causes them to attack.
		/// </summary>
		public void Attack()
		{
			while (mSleepingUnits.Count > 0)
			{
				mActiveUnits.Add(mSleepingUnits.Dequeue());
			}
		}

		/// <summary>
		/// Updates all currently active units positions and commands to attack if in range of an enemy.
		/// </summary>
		public void UpdateUnits(List<UpgradeBoost> boosts)
		{
			if (mActiveUnits.Count == 0)
				return;
			mFirstUnit = mActiveUnits[0];
			foreach (Unit unit in mActiveUnits)
			{
				if (Math.Abs(unit.Position - mStartingPosition) > Math.Abs(mFirstUnit.Position - mStartingPosition))
					mFirstUnit = unit;
				ManageBoostPerUnit(boosts, unit);

				if (mEnemy.mActiveUnits.Count == 0)
				{
					continue;
				}

				ManageAttackMove(unit);
			}
			ManageFood();
		}

		private static void ManageBoostPerUnit(List<UpgradeBoost> boosts, Unit unit)
		{
			UpgradeBoost boost = boosts.Find(x => Math.Abs(x.Position - unit.Position) < mDistanceToBoost);
			//Boost factor of zero means there is no boost
			if (boost.Factor != 0)
			{
				unit.MaxHealth *= boost.Factor;
				unit.Health *= boost.Factor;
				unit.Damage *= boost.Factor;
				unit.CalculateFoodCost();
				boosts.Remove(boost);
			}
		}

		private void ManageAttackMove(Unit unit)
		{
			Unit nearestEnemy = unit.FindNearestEnemy(mEnemy.ActiveUnits);
			int distanceToEnemy = nearestEnemy.Position - unit.Position;
			if (Math.Abs(distanceToEnemy) <= unit.Range)
			{
				unit.Attack(nearestEnemy);
				if (nearestEnemy.Health < 1)
				{
					mEnemy.ActiveUnits.Remove(nearestEnemy);
					mCoins += (int)(nearestEnemy.GoldCost * 0.5f * Utilities.CalculateRandomFactor());
				}
			}
			else
			{
				//Calculate the distanceToEnemy needed to get in range of the enemy
				int distanceToGetInRange = Math.Abs(distanceToEnemy) - unit.Range;
				//If the distanceToEnemy is negative, the target is to the left and the unit should move to the left, else move to the right
				if (distanceToEnemy < 0)
					unit.Position -= Math.Min(distanceToGetInRange, unit.Speed);
				else
					unit.Position += Math.Min(distanceToGetInRange, unit.Speed);
			}
		}

		private void ManageFood()
		{
			int coveredDistance = Math.Abs(mFirstUnit.Position - mStartingPosition);
			mFood += coveredDistance * FoodPerTile;
			foreach (var unit in mActiveUnits)
			{
				if (mFood >= unit.FoodCost)
				{
					mFood -= unit.FoodCost;
					if (unit.IsWeakened)
						unit.Strengthen();
				}
				else
				{
					if (!unit.IsWeakened)
						unit.Weaken();
				}
			}
		}

		#endregion
	}
}
